!function (w) {
    console.debug('HTTP data security transmission module v4.01')
    const ua = () => eval(atob('U3RyaW5nKHdpbmRvd1snQ3J5cHRvSlMnXVsnU0hBMSddKHdpbmRvd1snbmF2aWdhdG9yJ11bJ3VzZXJBZ2VudCddKSk='));
    const md5 = data => String(CryptoJS.HmacMD5(data, ua()));
    const sha1 = data => String(CryptoJS.HmacSHA1(data, ua()));
    const sha224 = data => String(CryptoJS.HmacSHA224(data, ua()));
    const sha384 = data => String(CryptoJS.HmacSHA384(data, ua()));
    const sha512 = data => String(CryptoJS.HmacSHA512(data, ua()));
    w.encrypt = w.exclude = true;
    w.secure = false;

    /**
     * FormData 转 JSON
     */
    JSON.toJSON = function (form) {
        if (!(form instanceof FormData)) return form;
        const json = {};
        for (const key of form.keys()) {
            const value = form.getAll(key);
            if (value == null) continue;
            json[key] = value.length === 1 ? value[0] : value;
        }
        return json;
    }

    /**
     * JSON 转 FormData
     */
    JSON.toForm = function (json) {
        if (json instanceof FormData) return json;
        const form = new FormData();
        for (let key of Object.keys(json)) {
            if (json[key] == null) continue;
            form.append(key, json[key]);
        }
        return form;
    }

    /**
     * 生成RSA密钥对
     */
    function generateRSAKey(jse) {
        return new Promise((resolve, reject) => {
            // 检查Crypto库
            if (!crypto.subtle) {
                return void resolve([jse.getPublicKeyB64(), jse.getPrivateKeyB64()])
            }
            // 使用Crypto生成RSA密钥
            crypto.subtle.generateKey({
                    name: "RSA-OAEP",
                    modulusLength: 1024,
                    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                    hash: {name: "SHA-512"},
                }, true, ["encrypt", "decrypt"]
            ).then(key => {
                crypto.subtle.exportKey("pkcs8", key.privateKey).then(pri => {
                    crypto.subtle.exportKey("spki", key.publicKey).then(pub => {
                        resolve([pub, pri].map(e => btoa(String.fromCharCode(...new Uint8Array(e)))))
                    }).catch(reason => reject(reason))
                }).catch(reason => reject(reason))
            }).catch(reason => reject(reason))
        })
    }

    /**
     * 文件散列MD5
     *
     * @param blob 文件
     * @return MD5
     */
    function digestBlob(blob) {
        return new Promise(resolve => {
            const reader = new FileReader();
            reader.readAsArrayBuffer(blob);
            reader.onload = () => {
                const digest = md5(CryptoJS.lib.WordArray.create(reader.result));
                console.debug('Generate file signature: ' + digest, blob)
                resolve(md5(blob.name + digest));
            }
        })
    }

    /**
     * 通用加密方法
     *
     * @param data 基本类型|数组|JSON|FormData
     * @param config 配置 {a: {encrypt}, b: {exclude}}
     * @return {string} 加密的结果
     */
    CryptoJS.encrypt = function (data, config) {
        // 空数据类型
        if (data == null) return ''
        // 基本数据类型
        if (["string", 'number', 'boolean'].includes(typeof data)) return String(data).encrypt();
        // 文件数据类型
        if (data instanceof Blob) throw Error('Not supported blob type')
        // 数组类型
        if (Array.isArray(data)) return data.join('\u1000').encrypt()
        // 复杂类型
        const encrypt = [], exclude = []
        if (config) {
            for (const field in config) {
                if (config.hasOwnProperty(field) && field) {
                    config[field].encrypt && encrypt.push(field);
                    config[field].exclude && exclude.push(field);
                }
            }
        }
        const form = JSON.toForm(data);
        // 排除
        exclude.forEach(key => form.delete(key));
        // 生成签名
        form.set('sign', CryptoJS.sign(form));
        // 整理数据
        for (const key of form.keys()) {
            const value = form.get(key);
            // 跳过文件处理
            if (value instanceof Blob) continue;
            form.set(key, encrypt.includes(key) ? CryptoJS.encrypt(value) : value);
        }
        return data instanceof FormData ? form : JSON.toJSON(form);
    }

    CryptoJS.exchange = (config) => new Promise((resolve, reject) => {
        const cipher = new JSEncrypt();
        // 开始交换公钥
        console.debug('Start exchanging public key...')
        fetch('/secure').then(async response => {
            const blob = await response.blob()
            if (!!blob.type) throw 'Invalid request data';
            const effective = time => Math.abs(~~(Date.now() / 1000) - time) < 0xf;
            // 扫描压缩文件
            console.debug('Verify response time')
            if (!effective(new Date(response.headers.get("Date")).getTime() / 1000)) {
                throw 'Response time timeout'
            }
            console.debug('Scan files...')
            const reader = new zip.ZipReader(new zip.BlobReader(blob));
            for (const entity of await reader.getEntries()) {
                console.debug('Current file: ' + entity.filename)
                // 时间双重检查
                const modtime = entity.lastModDate.getTime() / 1000;
                console.debug('Verify file write time...')
                if (effective(modtime)) {
                    // 检查文件签名
                    console.debug('Verify file name signature...')
                    if (sha224(entity.filename) === entity.comment) {
                        const _spk = btoa(String.fromCharCode(...await entity.getData(new zip.Uint8ArrayWriter())))
                        // 检查文件内容
                        console.debug('Verify file content...')
                        if (sha384(_spk) === entity.filename) {
                            return _spk;
                        }
                    }
                }
            }
            throw 'No key available to match'
        }).then(async _spk => {
            // 初始化RSA
            console.debug('Generate local RSA key')
            const keys = await generateRSAKey(cipher)
            // 设置服务器公钥
            cipher.setKey(_spk);
            // 把加密的切片发回服务器
            console.debug('Encrypt local public key')
            const slice = keys[0].length >> 1;
            const b35en = s => Array.from(cipher.encrypt(s))
                .map(e => e.charCodeAt(0).toString(35))
                .map(s => s.length < 2 ? '0' + s : s)
                .join('');
            return [sha1(_spk) + sha512(keys[0]), [b35en(keys[0].substr(0, slice)), b35en(keys[0].substr(slice))], keys[1]]
        }).then(async data => {
            const [signature, pubkeys, prikey] = data
            cipher.setKey(prikey);
            // 交换本地公钥
            console.debug('Exchange local public key...')
            return await fetch('/secure', {
                method: 'POST',
                headers: {
                    'content-type': "application/json",
                    'signature': signature.toUpperCase()
                },
                body: pubkeys.join('z').toUpperCase()
            })
        }).then(async response => {
            // 解析服务器AES密钥
            const text = await response.text()
            if (!text) throw 'Invalid response data'
            console.debug('Parse server key...')
            return CryptoJS.enc.Utf8.parse(cipher.decrypt(btoa(text.match(/\w{2}/g).map(a => String.fromCharCode(parseInt(a, 16))).join(''))))
        }).then(key => {
            function configure() {
                const iv = CryptoJS.enc.Hex.parse(String(key).slice(0, 2 << 4))
                const mode = CryptoJS.mode.CBC
                const pad = CryptoJS.pad.Pkcs7
                return {iv, mode, pad}
            }

            // 实现加密
            String.prototype.encrypt = function () {
                const min = 0x1000000000, max = 0xffffffffff;
                const random = (Math.floor(Math.random() * (max - min + 1)) + min).toString(16);
                let string = String(CryptoJS.AES.encrypt(random.concat(this), key, configure()));
                const safe = [[/\+/g, '-'], [/\//g, '_'], [/=/g, '']]
                safe.forEach(arr => string = string.replace(arr[0], arr[1]));
                return '<' + string + '>';
            }

            // 实现解密
            String.prototype.decrypt = function () {
                return CryptoJS.AES.decrypt(String(this), key, configure()).toString(CryptoJS.enc.Utf8);
            }

            // 实现签名
            CryptoJS.sign = function (...objs) {
                let _join = [], _time = Date.now().toString(36);
                for (const obj of objs) {
                    !function generate(obj) {
                        if (obj instanceof Blob) return
                        // 基本类型
                        if (["string", 'number', 'boolean'].includes(typeof obj)) _join.push(String(obj));
                        // Array
                        else if (Array.isArray(obj)) _join.push(obj.join('\u1000'));
                        // FormData 或 其他类型（JSON）
                        else Object.values(JSON.toJSON(obj)).forEach(value => generate(value))
                    }(obj)
                }
                const join = _join.join('')
                console.debug('Form splicing result: ' + join)
                const digest = sha1(_join.join('') + _time)
                console.debug('Generate request signature: ' + digest)
                return (digest + _time).encrypt()
            }

            // 实现签名文件
            CryptoJS.signBlob = async function (...objs) {
                const _join = [], _time = Date.now().toString(36);
                const sorter = (a, b) => a instanceof Blob && !(b instanceof Blob) ? 1 : -1
                for (const obj of objs.sort(sorter)) {
                    !await async function generate(obj) {
                        // 基本类型
                        if (["string", 'number', 'boolean'].includes(typeof obj)) _join.push(String(obj));
                        // Blob
                        else if (obj instanceof Blob) _join.push(await digestBlob(obj));
                        // Array
                        else if (Array.isArray(obj)) _join.push(obj.join('\u1000'));
                        // JSON
                        else for (const value of Object.values(JSON.toJSON(obj)).sort(sorter)) await generate(value);
                    }(obj)
                }
                const join = _join.join('')
                console.debug('Form splicing result: ' + join)
                const digest = sha1(_join.join('') + _time)
                console.debug('Generate request signature: ' + digest)
                return (digest + _time).encrypt()
            }
            // 清空密钥
            cipher.setKey(undefined)
            w.secure = true
            console.debug('Secure connection established complete.')
            resolve()
        }).catch(reason => {
            config && config.always && CryptoJS.exchange(config)
            reject(reason)
        })
    })
}(window);
